package com.spark.sniffer.plugin.plugins.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

import com.spark.sniffer.AgentArgs;
import com.spark.sniffer.logging.api.Logger;
import com.spark.sniffer.logging.api.LoggerFactory;
import com.spark.sniffer.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import com.spark.sniffer.plugin.interceptor.enhance.MethodInterceptResult;
import com.spark.sniffer.util.ReflectUtil;

/**
 * ACM拦截器，在拉取ACM数据时，尝试去拉取默认ACM配置，并覆盖合并至原有数据
 * 
 * @author cobe
 * @date 2019/12/27
 */
public class AcmPropertySourceBuilderInterceptor implements InstanceMethodsAroundInterceptor {
    private static final Logger log = LoggerFactory.getLogger(AcmPropertySourceBuilderInterceptor.class);

    private static final String ACM_PROPERTY_SRC_BUILDER_CLASS_NAME =
        "com.alibaba.cloud.acm.bootstrap.AcmPropertySourceBuilder";
    private static final String DEFAULT_ACM_PROPERTY_SRC_BUILDER_CLASS_NAME =
        "com.alibaba.cloud.acm.bootstrap.DefaultAcmPropertySourceBuilder";

    private boolean isInit = false;
    private Object defaultAcmPropertySourceBuilderInstance = null;

    @Override
    public void beforeMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable {
        if (isInit) {
            return;
        }

        // 只能在运行时初始化，否则因为加载次序的原因很可能还没加载到所需的类
        // 通过反射调用构造函数DefaultAcmPropertySourceBuilder(AcmPropertySourceBuilder)构造对象
        Class defaultAcmPropertySourceBuilderClass = null;
        try {
            // 注意，这里不能直接用Class.forName来加载，因为在不同的jar包中
            defaultAcmPropertySourceBuilderClass =
                Thread.currentThread().getContextClassLoader().loadClass(DEFAULT_ACM_PROPERTY_SRC_BUILDER_CLASS_NAME);
        } catch (ClassNotFoundException e) {
            log.warn("ClassNotFound: {}", e.getMessage());
            // 没有DefaultAcmPropertySourceBuilder，不进行处理
            isInit = true;
            return;
        }

        try {
            Class acmPropertySourceBuilderClass =
                Thread.currentThread().getContextClassLoader().loadClass(ACM_PROPERTY_SRC_BUILDER_CLASS_NAME);

            defaultAcmPropertySourceBuilderInstance = defaultAcmPropertySourceBuilderClass
                .getDeclaredConstructor(acmPropertySourceBuilderClass).newInstance(instance);

            log.info("{} instance created.", DEFAULT_ACM_PROPERTY_SRC_BUILDER_CLASS_NAME);
            isInit = true;
        } catch (ClassNotFoundException e) {
            log.warn("ClassNotFound: {}", e.getMessage());
        } catch (InvocationTargetException e) {
            // 不清楚为什么会产生这个错误，但应用启动时会重复加载几次ACM，几次之后这里就可以正常运行了
        }

    }

    @Override
    public Object afterMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable {
        if (defaultAcmPropertySourceBuilderInstance == null) {
            return ret;
        }

        // 查询默认的ACM数据
        Properties props =
            ReflectUtil.getMethod(defaultAcmPropertySourceBuilderInstance, "loadDefaultAcmData").invoke();

        // 覆盖合并原有返回值
        Properties overridedProperties = overrideProperties((Properties)ret, props);
        printProperties(overridedProperties, (String)allArguments[0]);
        return overridedProperties;
    }

    @Override
    public void handleMethodException(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Throwable t) {}

    private Properties overrideProperties(Properties original, Properties override) {
        if (AgentArgs.isLocal()) {
            log.info("ACM data not overrided on local mode");
            return original;
        }

        if (original == null) {
            return override;
        }
        if (override == null) {
            return original;
        }

        original.putAll(override);
        return original;
    }

    private void printProperties(Properties props, String dataId) {
        if (props == null) {
            return;
        }

        log.info("ACM data [{}] overrided as:", dataId);
        props.forEach((k, v) -> log.info("=> {}={}", k, v));
    }

}
